package run.bottle.securityauth.converter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import run.bottle.common.context.ThreadContext;
import run.bottle.common.entity.AppUser;
import run.bottle.urp.service.UserService;

import java.util.*;

/**
 * @author liyc
 * @date 2022-03-30
 */
public class CustomJwtAuthenticationConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

    private final Logger logger = LoggerFactory.getLogger(CustomJwtAuthenticationConverter.class);

    private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";
    private static final Collection<String> WELL_KNOWN_AUTHORITIES_CLAIM_NAMES = Arrays.asList("scope", "scp");
    private String authorityPrefix = DEFAULT_AUTHORITY_PREFIX;
    private String authoritiesClaimName;
    private UserService userService;

    public CustomJwtAuthenticationConverter(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Collection<GrantedAuthority> convert(final Jwt jwt) {

        String subject = jwt.getSubject();

        logger.info("username:{}", subject);

        Object claimsAud = jwt.getClaims().getOrDefault(JwtClaimNames.AUD, new ArrayList<>());
        if (((List<String>) claimsAud).contains("user-client") || ((List<String>) claimsAud).contains("login-client")) {
            AppUser appUser = userService.getAppUserByUsername(subject);

            ThreadContext.setCurrentUser(appUser);
            ThreadContext.setAccessToken(jwt.getTokenValue());

            logger.info("user:{}", appUser.toString());
        }

        Collection<GrantedAuthority> grantedAuthorities = new ArrayList();
        Iterator var3 = this.getAuthorities(jwt).iterator();

        while(var3.hasNext()) {
            String authority = (String)var3.next();
            grantedAuthorities.add(new SimpleGrantedAuthority(this.authorityPrefix + authority));
            logger.info("authority:" + authority);
        }

        return grantedAuthorities;
    }

    public void setAuthorityPrefix(String authorityPrefix) {
        Assert.notNull(authorityPrefix, "authorityPrefix cannot be null");
        this.authorityPrefix = authorityPrefix;
    }

    public void setAuthoritiesClaimName(String authoritiesClaimName) {
        Assert.hasText(authoritiesClaimName, "authoritiesClaimName cannot be empty");
        this.authoritiesClaimName = authoritiesClaimName;
    }

    private String getAuthoritiesClaimName(Jwt jwt) {
        if (this.authoritiesClaimName != null) {
            return this.authoritiesClaimName;
        } else {
            Iterator var2 = WELL_KNOWN_AUTHORITIES_CLAIM_NAMES.iterator();

            String claimName;
            do {
                if (!var2.hasNext()) {
                    return null;
                }

                claimName = (String)var2.next();
            } while(!jwt.hasClaim(claimName));

            return claimName;
        }
    }

    private Collection<String> getAuthorities(Jwt jwt) {
        String claimName = this.getAuthoritiesClaimName(jwt);
        if (claimName == null) {
            logger.error("Returning no authorities since could not find any claims that might contain scopes");
            return Collections.emptyList();
        } else {
            Object authorities = jwt.getClaim(claimName);
            if (authorities instanceof String) {
                return StringUtils.hasText((String)authorities) ? Arrays.asList(((String)authorities).split(" ")) : Collections.emptyList();
            } else {
                return (Collection)(authorities instanceof Collection ? this.castAuthoritiesToCollection(authorities) : Collections.emptyList());
            }
        }
    }

    private Collection<String> castAuthoritiesToCollection(Object authorities) {
        return (Collection)authorities;
    }
}
